// This file is shared between the frontend and server code of web, so that
// filename validation is the same in both implementations.
// The logic in all copies must be kept in sync:
//   app/src/Features/Project/SafePath.js
//   frontend/js/ide/directives/SafePath.js
//   frontend/js/features/file-tree/util/safe-path.js

const BADCHAR_RX = new RegExp(
  `\
[\
\\/\
\\\\\
\\*\
\\u0000-\\u001F\
\\u007F\
\\u0080-\\u009F\
\\uD800-\\uDFFF\
]\
`,
  'g'
)

const BADFILE_RX = new RegExp(
  `\
(^\\.$)\
|(^\\.\\.$)\
|(^\\s+)\
|(\\s+$)\
`,
  'g'
)

// Put a block on filenames which match javascript property names, as they
// can cause exceptions where the code puts filenames into a hash. This is a
// temporary workaround until the code in other places is made safe against
// property names.
//
// The list of property names is taken from
//   ['prototype'].concat(Object.getOwnPropertyNames(Object.prototype))
const BLOCKEDFILE_RX = new RegExp(`\
^(\
prototype\
|constructor\
|toString\
|toLocaleString\
|valueOf\
|hasOwnProperty\
|isPrototypeOf\
|propertyIsEnumerable\
|__defineGetter__\
|__lookupGetter__\
|__defineSetter__\
|__lookupSetter__\
|__proto__\
)$\
`)

const MAX_PATH = 1024 // Maximum path length, in characters. This is fairly arbitrary.

export function clean(filename) {
  filename = filename.replace(BADCHAR_RX, '_')
  // for BADFILE_RX replace any matches with an equal number of underscores
  filename = filename.replace(BADFILE_RX, match =>
    new Array(match.length + 1).join('_')
  )
  // replace blocked filenames 'prototype' with '@prototype'
  filename = filename.replace(BLOCKEDFILE_RX, '@$1')
  return filename
}

export function isCleanFilename(filename) {
  return (
    isAllowedLength(filename) &&
    !filename.match(BADCHAR_RX) &&
    !filename.match(BADFILE_RX)
  )
}

export function isBlockedFilename(filename) {
  return BLOCKEDFILE_RX.test(filename)
}

// returns whether a full path is 'clean' - e.g. is a full or relative path
// that points to a file, and each element passes the rules in 'isCleanFilename'
export function isCleanPath(path) {
  const elements = path.split('/')

  const lastElementIsEmpty = elements[elements.length - 1].length === 0
  if (lastElementIsEmpty) {
    return false
  }

  for (let element of Array.from(elements)) {
    if (element.length > 0 && !isCleanFilename(element)) {
      return false
    }
  }

  // check for a top-level reserved name
  if (BLOCKEDFILE_RX.test(path.replace(/^\/?/, ''))) {
    return false
  } // remove leading slash if present

  return true
}

export function isAllowedLength(pathname) {
  return pathname.length > 0 && pathname.length <= MAX_PATH
}
